package client

import (
	"errors"
	"fmt"
	"time"

	"gitee.com/yuebanlaosiji/gocelery/pkg/backend"
	"gitee.com/yuebanlaosiji/gocelery/pkg/broker"
	"gitee.com/yuebanlaosiji/gocelery/pkg/protocol"
	"gitee.com/yuebanlaosiji/gocelery/pkg/types"
	"github.com/google/uuid"
)

var (
	// ErrMissingBroker is returned when broker is not provided
	ErrMissingBroker = errors.New("broker is required")

	// ErrTimeout is returned when waiting for a result times out
	ErrTimeout = errors.New("timeout waiting for result")

	// ErrNoBackend is returned when trying to get a result with no backend
	ErrNoBackend = errors.New("no result backend configured")
)

// ClientImpl implements the Client interface
type ClientImpl struct {
	broker    broker.Broker
	backend   backend.Backend
	protocol  protocol.Protocol
	queueName string
}

// ClientOptions contains options for creating a client
type ClientOptions struct {
	QueueName string
}

// NewClient creates a new client
func NewClient(
	brk broker.Broker,
	bck backend.Backend,
	proto protocol.Protocol,
	options *ClientOptions,
) (*ClientImpl, error) {
	if brk == nil {
		return nil, ErrMissingBroker
	}

	// Use default options if not provided
	if options == nil {
		options = &ClientOptions{
			QueueName: "celery",
		}
	}

	client := &ClientImpl{
		broker:    brk,
		backend:   bck,
		protocol:  proto,
		queueName: options.QueueName,
	}

	return client, nil
}

// SendTask sends a task to the queue
func (c *ClientImpl) SendTask(task types.Task) (string, error) {
	// Generate a UUID if ID is not provided
	if task.ID == "" {
		task.ID = uuid.New().String()
	}

	// Create task message
	msg, err := c.protocol.CreateTaskMessage(task)
	if err != nil {
		return "", fmt.Errorf("failed to create task message: %w", err)
	}

	// Send to broker
	err = c.broker.SendMessage(c.queueName, msg)
	if err != nil {
		return "", fmt.Errorf("failed to send message: %w", err)
	}

	return task.ID, nil
}

// GetResult gets the result of a task
func (c *ClientImpl) GetResult(taskID string) (interface{}, error) {
	if c.backend == nil {
		return nil, ErrNoBackend
	}

	// Check if result exists
	exists, err := c.backend.HasResult(taskID)
	if err != nil {
		return nil, fmt.Errorf("failed to check if result exists: %w", err)
	}

	if !exists {
		// No result yet
		return nil, nil
	}

	// Get the result bytes
	resultBytes, err := c.backend.GetResult(taskID)
	if err != nil {
		return nil, fmt.Errorf("failed to get result: %w", err)
	}

	// Parse the result
	result, err := c.protocol.ParseResultMessage(resultBytes)
	if err != nil {
		return nil, fmt.Errorf("failed to parse result: %w", err)
	}

	// Check result status
	if result.Status == types.StatusFailure {
		return nil, errors.New(result.Error)
	}

	return result.Result, nil
}

// WaitForResult waits for a task result with timeout
func (c *ClientImpl) WaitForResult(taskID string, timeout time.Duration) (interface{}, error) {
	if c.backend == nil {
		return nil, ErrNoBackend
	}

	deadline := time.Now().Add(timeout)
	pollInterval := 100 * time.Millisecond

	for time.Now().Before(deadline) {
		result, err := c.GetResult(taskID)
		if err != nil {
			return nil, err
		}

		// If we got a non-nil result, task is complete
		if result != nil {
			return result, nil
		}

		// Wait before trying again
		time.Sleep(pollInterval)
	}

	return nil, ErrTimeout
}
